<?php

// CREATE FUNCTION based on sprintf that accepts a formot and params for a db query and escapes the information propery:
// DB::FormatQuery("SELECT %f FROM %t WHERE %f = %s OR %f IN %a;", array('ID', 'Name', 'Value'), 'Table', 'Conditional1', 'value1', 'Conditional2', array('one', 'two', 'three'));
// SELECT `ID`, `Name`, `Value` FROM `Table` WHERE `Conditional1` = 'value1' OR `Conditional2` IN ('one', 'two', 'three');
// Sleep on this. maybe theres a better way.

// Also implement read and write callbacks on classes

require_once FRAMEWORK_PATH . 'prototype.php';

define('PRELOAD_DB', false);

define('SELECT', 'SELECT');
define('UPDATE', 'UPDATE');
define('DELETE', 'DELETE');
define('INSERT', 'INSERT');

define('EQ',   '=');
define('NEQ',  '!=');
define('LT',   '<');
define('LTE',  '<=');
define('GT',   '>');
define('GTE',  '>=');

define('IN',   'IN');
define('BET',  'BETWEEN');
define('NUL',  'IS NULL');
define('LIK',  'LIKE');
define('EX',   'EXISTS');
define('RLIK', 'REGEXP');

define('NIN',  'NOT IN');
define('NBET', 'NOT BETWEEN');
define('NNUL', 'IS NOT NULL');
define('NLIK', 'NOT LIKE');
define('NEX',  'NOT EXISTS');
define('NRLIK','NOT REGEXP');

define('FUNC', 'FUNC');

define('ASC',  'ASC');
define('DESC', 'DESC');

define('LEFT',  0x01);
define('RIGHT', 0x02);
define('INNER', 0x04);
define('OUTER', 0x08);
define('CROSS', 0x10);

define('DATE_DEFAULT',    'Y-m-d H:i:s');
define('DATE_DATEATTIME', 'F jS Y \a\t g:ia');
//define('DATE_FUZZY',      '@@DBDateTime::FuzzyTime');


/*
 * __autoload
 */
function __autoload($class_name) {
	if (substr($class_name, -5) == 'Table') {
		$table_name = substr($class_name, 0, -5);
	} else if (substr($class_name, -6) == 'Record') {
		$table_name = substr($class_name, 0, -6);
	} else {
		return false;
	}

	return !!DB::Table($table_name);
}


/*
 * DB
 */
class DB extends Prototype {

	const MYSQL    = 1;
	const PGSQL    = 2;
	const MSSQL    = 3;

	// removing distinct saved about 1ms per query
	// removing ORDER BY ORDINAL_POSITION seems to save some time too.
	// const InformationSchemaQuery = "SELECT `COLUMN_NAME`, `DATA_TYPE`, `COLUMN_KEY`, `IS_NULLABLE`, `COLUMN_COMMENT`, `COLUMN_DEFAULT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = '%s' AND `TABLE_NAME` = '%s';";
	const InformationSchemaQuery = "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_KEY, IS_NULLABLE, COLUMN_COMMENT, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';";

	const AuditQueries = true;

	private static $engine;

	private static $schema;
	private static $link;
	private static $tables = array();

	private static $queryCount = 0;
	private static $trueQueryCount = 0;
	private static $queries = array();

	public static $cache = array();

	private static $null;
	private static $stats = array(
		'affected' => null,
		'numrows'  => null,
		'insertid' => null,
		'error'    => null,
		'errno'    => null
	);

	/*
	 * Connect
	 */
	public static function Connect($params) {
		extract($params);
		try {
			switch ($engine) {
				case self::MYSQL:
					self::$link = mysql_pconnect($server, $username, $password);
					mysql_select_db($schema = $database);
					break;

				case self::PGSQL:
					$keys = array(
						'host'			=> 1,
						'hostaddr'		=> 1,
						'port'			=> 1,
						'dbname'		=> 1,
						'user'			=> 1,
						'password'		=> 1,
						'connect_timeout'	=> 1,
						'options'		=> 1,
						'tty'			=> 1,
						'sslmode'		=> 1,
						'service'		=> 1
					);

					$params = array_intersect_key($params, $keys);

					foreach ($params as $key => $value) {
						$connectionParams[] = $key . "='" . addcslashes($value, "'\\") . "'"; // '
					}

					$connectionString = implode(' ', $connectionParams);
					$schema = $dbname;

					self::$link = pg_pconnect($connectionString);
					break;

				case self::MSSQL:
					self::$link = mssql_pconnect($servername, $username, $password);
					mssql_select_db($schema = $database);

				default: return false;
			}

			self::$engine = $engine;
			self::$schema = $schema;

			self::LoadDBClassFile();

			return true;
		} catch(Exception $ex) {
			return false;
		}
	}


	/*
	 * ConnectFromConfig()
	 */
	public static function ConnectFromConfig() {
		global $config;

		if (isset($config['db'])) {
			return self::Connect($config['db']);
		}
	}


	/*
	 * ConnectionStringUUID()
	 */
	public static function ConnectionStringUUID() {
		global $config;

		if (isset($config['db']['uuid'])) {
			return $config['db']['uuid'];
		} else if (isset($config['db'])) {
			return $config['db']['uuid'] = sha1(implode($config['db'], '%%'));
		}

		return false;
	}


	/*
	 * LoadDBClassFile()
	 */
	public static function LoadDBClassFile($table = null) {
		$preload = defined('PRELOAD_DB') && constant('PRELOAD_DB') === true;

		$path = INCLUDE_PATH . 'db/' . self::ConnectionStringUUID();

		if (is_null($table) && $preload && file_exists($path . '.php')) {
			include_once $path . '.php';
		} else if (!is_null($table) && !$preload && file_exists($path . '/' . $table . '.php')) {
			include_once $path . '/' . $table . '.php';
		} else {
			return false;
		}

		return $table . 'Table';
	}


	/*
	 * TableLoaded()
	 */
	public static function TableLoaded($table) {
		self::$tables[$table] = $table . 'Table';
	}


	/*
	 * Open()
	 * DEPRECATED (but still functional)
	 */
	public static function Open(array $p) {
		$p['engine'] = self::MYSQL;
		$p['username'] = $p['user'];
		$p['password'] = $p['pass'];
		$p['database'] = $p['schema'];

		return self::Connect($p);
	}


	/*
	 * Table()
	 */
	public static function Table($table) {
		if (isset(self::$tables[$table])) {
			$className = self::$tables[$table];
		} else {
			if (!self::LoadDBClassFile($table)) {
				if (!self::CreateTableReference($table)) {
					return false;
				}
			}
			$className = $table . 'Table';
		}

		return new $className();
	}


	/*
	 * RefreshSchema()
	 */
	public static function RefreshSchema() {
		$schema = self::QuoteEscapeString(self::$schema);
		$schemaCache = DB::QuoteIdentifier('SchemaCache');
		$schemaData = DB::QuoteIdentifier('SchemaData');
		$schemaField = DB::QuoteIdentifier('SCHEMA');

		self::Query("TRUNCATE TABLE $schemaCache;", false);
		self::Query("INSERT INTO $schemaCache SELECT * FROM $schemaData WHERE $schemaField = $schema;", false);
	}


	/*
	 * RefreshCache()
	 */
	public static function RefreshCache() {
		return false;
	}


	public static function Cache($table, $id, $record = null, $clear = false) {
		if (!isset(self::$cache[$table])) {
			self::$cache[$table] = array();
		}

		if (func_num_args() >= 3 && !DB::IsNull($record)) {
			self::$cache[$table][$id] = $record;
			return;
		} else if (isset(self::$cache[$table][$id])) {
			if (!$clear) {
				return self::$cache[$table][$id];
			} else {
				unset(self::$cache[$table][$id]);
				return;
			}
		} else {
			return false;
		}
	}


	public static function CreateTableReference($table) {
		$class = self::GenerateTableClass($table);
		if ($class) {
			eval($class);
			return self::$tables[$table] = $table . 'Table';
		}
	}


	public static function PrepareQuery($format) {

	}


	public static function GenerateTableClass($table) {
		$default = '';
		$updateFields = array();
		$schemaCode = array();
		$fkFuncs = array();
		$fkSchema = array();
		$keys = array();
		$pk = 'null';
		$pk_unique = 'false';
		$query = sprintf(self::InformationSchemaQuery, self::$schema, $table);
		$res = self::Query($query, false);

		if (self::NumRows($res) == 0) {
			return false;
		}

		while ($row = self::FetchAssoc($res)) {
			$schema[$row['COLUMN_NAME']] = array(
				'name' => $row['COLUMN_NAME'],
				'type' => $row['DATA_TYPE'],
				'nullable' => $row['IS_NULLABLE'] == 'YES',
				'comment' => $row['COLUMN_COMMENT']
			);

			if ($row['COLUMN_KEY'] == 'PRI') {
				$pk = $row['COLUMN_NAME'];
				$pk_unique = $row['DATA_TYPE'] != 'int' ? 'true' : 'false';
			}

			if ($row['COLUMN_COMMENT'] == 'default') {
				$default = $row['COLUMN_NAME'];
			}
		}

		self::FreeResult($res);

		$schemaCache = DB::QuoteIdentifier('SchemaCache');
		$schemaField = DB::QuoteIdentifier('SCHEMA');
		$tableField = DB::QuoteIdentifier('TABLE');
		$schemaEscQuot = DB::QuoteEscapeString(self::$schema);
		$tableEscQuot = DB::QuoteEscapeString($table);

		$query = "SELECT * FROM $schemaCache WHERE $schemaField = $schemaEscQuot AND $tableField = $tableEscQuot;";
		$res = self::Query($query, false);

		while ($row = self::FetchAssoc($res)) {
			$fkSchema[$row['NAME']] = array(
				'reftbl' => $row['REF_TABLE'],
				'refcol' => $row['REF_COLUMN'],
				'col' => $row['COLUMN'],
				'sel' => $row['REF_KEY'] == 'MUL' ? 'Select' : 'Single'
			);

			$key = DB::QuoteIdentifier($row['REF_TABLE']) . '.' . DB::QuoteIdentifier($row['REF_COLUMN']);
			$keys[$key] = DB::QuoteIdentifier($row['COLUMN']);
		}

		self::FreeResult($res);

		$schema = var_export($schema, true);
		$fkSchema = var_export($fkSchema, true);
		$keys = var_export($keys, true);

		$db = self::$schema;

		$class =
"class {$table}Table extends DBTableParent {
	protected \$_db = '$db';
	protected \$_table = '$table';
	protected \$_recordClass = '{$table}Record';
	protected \$_schema = $schema;
	protected \$_pk = '$pk';
	protected \$_pk_unique = $pk_unique;
	protected \$_keys = $keys;
}

class {$table}Record extends DBRecordParent {
	protected \$_db = '$db';
	protected \$_table = '$table';
	protected \$_schema = $schema;
	protected \$_pk = '$pk';
	protected \$_fkSchema = $fkSchema;
	protected \$_pk_unique = $pk_unique;
	protected \$_default = '$default';
}";

		return $class;
	}

	public static function IsSQLNumericType($column, $schema) {
		switch ($schema[$column]['type']) {
			case 'tinyint':
			case 'smallint':
			case 'mediumint':
			case 'int':
			case 'bigint':
			case 'float':
			case 'double':
			case 'real':
			case 'decimal':
				return true;
			default:
				return false;
		}
	}

	public static function Query($query, $audit = true) {
		self::$trueQueryCount++;

		switch (self::$engine) {
			case self::MYSQL:
				//$res = @mysql_unbuffered_query($query, self::$link);
				$res = @mysql_query($query, self::$link);
				break;
			case self::PGSQL:
				$res = @pg_query($query, self::$link);
				break;
			case self::MSSQL:
				$res = @mssql_query($query, self::$link);
				break;
			default:
				return false;
		}

		if ($res && $audit) {
			self::$queryCount++;
			self::RecordStats($res);

			if (self::AuditQueries === true) {
				self::QueryAudit($query, self::$stats['errno'], self::$stats['error'], self::$stats['numrows'], self::$stats['affected']);
			}
		}

		if (!$res) {
			$error = mysql_error(self::$link);
			$errno = mysql_errno(self::$link);

			//self::QueryAudit($query, $errno, $error);

			throw new DBException($error, $errno, $query);
		}

		return $res;
	}

	public static function QueryAudit($query, $errorno = false, $error = false, $numrows = false, $affectedrows = false) {
		self::$queries[] = $query;

		$query = self::QuoteEscapeString($query);
		$error = $error ? DB::QuoteEscapeString($error) : 'NULL';
		$errorno = $errorno ? $errorno : 'NULL';
		$numrows = $numrows ? $numrows : 'NULL';
		$affectedrows = $affectedrows ? $affectedrows : 'NULL';

		$queryAudit = DB::QuoteIdentifier('QueryAudit');
		$queryField = DB::QuoteIdentifier('Query');
		$numRowsField = DB::QuoteIdentifier('NumRows');
		$affRowsField = DB::QuoteIdentifier('AffectedRows');
		$errNoField = DB::QuoteIdentifier('ErrorNo');
		$errorField = DB::QuoteIdentifier('Error');

		// Considered INSERT DELAYED. Small performance hit with it? (When the DB isn't very taxed though, might be a benefit if DB is very busy)
		self::Query("INSERT $queryAudit ($queryField, $numRowsField, $affRowsField, $errNoField, $errorField) VALUES ($query, $numrows, $affectedrows, $errorno, $error);", false);
	}

	public static function QueryCount() {
		return self::$queryCount;
	}

	public static function TrueQueryCount() {
		return self::$trueQueryCount;
	}

	public static function GetQueries() {
		return self::$queries;
	}

	public static function FreeResult($res) {
		if (is_resource($res)) {
			switch (self::$engine) {
				case self::MYSQL:
					@mysql_free_result($res);
					break;
				case self::PGSQL:
					@pg_free_result($res);
					break;
				case self::MSSQL:
					@mssql_free_result($res);
					break;
			}
		}
	}

	public static function FetchAssoc($res) {
		if (is_resource($res)) {
			switch (self::$engine) {
				case self::MYSQL:
					return @mysql_fetch_assoc($res);
				case self::PGSQL:
					return @pg_fetch_assoc($res);
				case self::MSSQL:
					return @mssql_fetch_assoc($res);
			}
		}
	}

	public static function FetchRow($res) {
		if (is_resource($res)) {
			switch (self::$engine) {
				case self::MYSQL:
					return @mysql_fetch_row($res);
				case self::PGSQL:
					return @pg_fetch_row($res); // guess
				case self::MSSQL:
					return @mssql_fetch_row($res); //guess
			}
		}
	}

	public static function NumRows($res) {
		if (is_resource($res)) {
			switch (self::$engine) {
				case self::MYSQL:
					return @mysql_num_rows($res);
				case self::PGSQL:
					return @pg_num_rows($res);
				case self::MSSQL:
					return @mssql_num_rows($res);
			}
		}
	}

	public static function AffectedRows() {
		return self::$stats['affected'];
	}

	public static function InsertID() {
		return self::$stats['insertid'];
	}

	public static function Error() {
		return self::$stats['error'];
	}

	public static function ErrorNumber() {
		return self::$stats['errno'];
	}

	public static function EscapeString($string) {
		switch (self::$engine) {
			case self::MYSQL:
				return mysql_real_escape_string($string);
			case self::PGSQL:
				return pg_escape_string($string);
			case self::MSSQL:
				return str_replace("'", "''", $string); // '
			default:
				return;
		}
	}

	public static function QuoteIdentifier($string) {
		switch (self::$engine) {
			case self::MYSQL:
				return "`$string`";
			case self::PGSQL:
				return "\"$string\"";
			case self::MSSQL:
				return "[$string]";
			default:
				return;
		}
	}

	public static function QuoteEscapeString($string) {
		return "'" . self::EscapeString($string) . "'";
	}

	public static function Null() {
		return (self::$null instanceof DBNull) ? self::$null : self::$null = new DBNull();
	}

	public static function IsNull($obj) {
		if (func_num_args() == 1) {
			return $obj === null || $obj instanceof DBNull;
		} else {
			$args = func_get_args();

			foreach ($args as $arg) {
				if ($arg === null || $arg instanceof DBNull) {
					return true;
				}
			}

			return false;
		}
	}

	public static function IfNull() {
		$args = func_get_args();

		foreach ($args as $arg) {
			if (!($arg === null || $arg instanceof DBNull)) {
				return $arg;
			}
		}

		return self::Null();
	}

	public static function Concat() {
		$args = func_get_args();
		$out = '';

		foreach ($args as $arg) {
			if ($arg === null || $arg instanceof DBNull) {
				return self::Null();
			} else {
				$out .= $arg;
			}
		}

		return $out;
	}

	private static function RecordStats($res) {
		switch (self::$engine) {
			case self::MYSQL:
				self::$stats = array(
					'affected'	=> mysql_affected_rows(self::$link),
					'insertid'	=> mysql_insert_id(self::$link),
					'errno'		=> !$res ? mysql_errno(self::$link) : null,
					'error'		=> !$res ? mysql_error(self::$link) : null,
					'numrows'	=> $res ? @mysql_num_rows($res) : null
				);
				break;

			case self::PGSQL:
				self::$stats = array(
					'affected'	=> pg_affected_rows(self::$link),
					'insertid'	=> null, //mysql_insert_id(self::$link),
					'errno'		=> null, //!$res ? pg_errno(self::$link) : null,
					'error'		=> !$res ? pg_last_error(self::$link) : null,
					'numrows'	=> $res ? pg_num_rows($res) : null
				);
				break;

			case self::MSSQL:
				self::$stats = array(
					'affected'	=> mssql_rows_affected(self::$link),
					'insertid'	=> null,
					'errno'		=> null, //!$res ? mysql_errno(self::$link) : null,
					'error'		=> null, //!$res ? mysql_error(self::$link) : null,
					'numrows'	=> $res ? mssql_num_rows($res) : null
				);
				break;
		}
	}


	public static function UniqueID() {
		$hash = md5(uniqid(mt_rand(), true));

		switch (self::$engine) {
			default:
				return $hash;
			case self::MSSQL:
				$y = array('8', '9', 'a', 'b');
				return sprintf( '%s-%s-4%s-%s%s-%s',
					substr($hash, 0, 8),
					substr($hash, 8, 4),
					substr($hash, 12, 3),
					$y[mt_rand(0, 3)],
					substr($hash, 15, 3),
					substr($hash, 18, 12)
				);
		}
	}

	/*
	 * __callStatic
	 * Magic function to allow instantiation of DB Objects by name
	 */
	public static function __callStatic($func, $args) {
		return DB::Table($func);
	}

	/*
	 * __construct
	 */
	private function __construct() {}

}

/*
 * DBTableParent
 */
abstract class DBTableParent extends Prototype implements IteratorAggregate, Countable {

	protected $_db;
	protected $_table;
	protected $_pk;
	protected $_recordClass;

	protected $_schema = array();

	protected $_selectFields = array();

	protected $_joins = array();
	protected $_where = array();
	protected $_whereCallback = array();
	protected $_orderby = array();
	protected $_groupby = array();
	protected $_limit = array();

	protected $_distinct = false;
	protected $_index = false;


	/*
	 * ID()
	 * Implement this better, do the actualy query here, dont farm it out to Where->Single
	 * And for gods sake use some damn caching, you lazy ass.
	 * Doesn't account for deleted records when not deleted by ID.
	 */
	public function ID($id) {
		$record = DB::Cache($this->_table, $id);
		if (!$record) {
			$record = DB::Table($this->_table)->Where($this->_pk, EQ, $id)->Single();
		}
		return $record;
	}


	/*
	 * Where Functions only processed on Select statements
	 */
	public function Where($field, $op, $value1 = false, $value2 = false) {

		switch (strtoupper($op)) {
			default: return $this;

			case LT:
			case LTE:
			case GT:
			case GTE:

			case EQ:
			case IN:
			case BET:
			case NUL:
			case LIK:
			case EX:
			case RLIK:

			case NEQ:
			case NIN:
			case NBET:
			case NNUL:
			case NLIK:
			case NEX:
			case NRLIK:

			case FUNC:
		}

		if ($op == FUNC && !function_exists($field)) {
			return $this;
		} else if ($op == FUNC && function_exists($field)) {
			$this->_whereCallback[] = $field;
			return $this;
		} else if ($op == EX || $op == NEX) {
			$nom = DB::QuoteIdentifier($this->_table) . '.' . DB::QuoteIdentifier($this->_pk);

			if (isset($field->_keys[$nom])) {
				if ($field instanceof DBTableParent) {
					$q = clone $field;

					$q->_orderby = array();
					$q->_groupby = array();
					$q->_distinct = false;

					$q->_where[] = sprintf("(%s = %s)", $nom, $q->_keys[$nom]);
					$subselect = $q->QueryString('SELECT', '1');

					$this->_where[] = sprintf("%s (%s)", $op, $subselect);

					return $this;
				} else if ($field instanceof DBRecordCollection) {
					// TODO: DOESNT WORK, FIX
					$rel_field = $field->_keys[$nom];
					$values = array();
					foreach ($field as $row) {
						$value = $row->GetValue($rel_field);
						if (!DB::IsNull($value)) {
							$values[] = $value;
						}
					}

					$op = IN;
					$field = $this->_pk;
					$value1 = array_unique($values);
				} else {
					return $this;
				}
			} else {
				return $this;
			}
		}

		if (!is_scalar($field) || !array_key_exists($field, $this->_schema)) {
			return $this;
		}

		$type = $this->_schema[$field]['type'];

		if ($type == 'timestamp' || $type == 'datetime') {
			if (is_object($value1) && $value1 instanceof DBDateTime) {
				$value1 = $value1->Default;
			} else if (is_numeric($value1)) {
				$value1 = date(DATE_DEFAULT, $value1);
			}

			if (is_object($value2) && $value2 instanceof DBDateTime) {
				$value2 = $value2->Default;
			} else if (is_numeric($value2)) {
				$value2 = date(DATE_DEFAULT, $value2);
			}
		} else if (DB::IsSQLNumericType($field, $this->_schema) && is_bool($value1)) {
			$value1 = $value1 ? 1 : 0;
		}

		if ($op == EQ || $op == NEQ) {
			if (DB::IsNull($value1)) {
				$op = $op == EQ ? NUL : NNUL;
				$this->_where[] = sprintf('(%s %s)', DB::QuoteIdentifier($field), $op);
			} else if ($value1 instanceof DBTableParent) {
				$subselect = clone $value1;
				$subselect->Limit(1);
				$this->_where[] = sprintf('(%s %s (%s))', DB::QuoteIdentifier($field), $op, $subselect->QueryString('SELECT', false));
			} else if (is_scalar($value1)) {
				$this->_where[] = sprintf('(%s %s %s)', DB::QuoteIdentifier($field), $op, is_string($value1) ? DB::QuoteEscapeString($value1) : $value1);
			}
			return $this;
		}

		if( $op == NUL || $op == NNUL ) {
			$this->_where[] = sprintf( '(%s %s)', DB::QuoteIdentifier( $field ), $op );
		} else if ($op == IN || $op == NIN) {
			if (is_array($value1)) {
				foreach ($value1 as $element) {
					if (DB::IsSQLNumericType($field, $this->_schema) && is_numeric($element)) {
						$elements[] = $element;
					} else {
						$elements[] = DB::QuoteEscapeString($element);
					}
				}
				$this->_where[] = sprintf('(%s %s (%s))', DB::QuoteIdentifier($field), $op, implode($elements, ', '));
			}
			else if ($value1 instanceof DBTableParent) {
				$this->_where[] = sprintf('(%s %s (%s))', DB::QuoteIdentifier($field), $op, $value1->QueryString('SELECT', false));
			}
		} else if ($op == BET) {
			if (!(DB::IsSQLNumericType($field, $this->_schema) && is_numeric($value1) && is_numeric($value2))) {
				$value1 = DB::QuoteEscapeString($value1);
				$value2 = DB::QuoteEscapeString($value2);
			}

			$this->_where[] = sprintf('(%s %s %s AND %s)', DB::QuoteIdentifier($field), $op, $value1, $value2);
		} else if(!DB::IsSQLNumericType($field, $this->_schema) && is_string($value1)) {
			$this->_where[] = sprintf('(%s %s %s)', DB::QuoteIdentifier($field), $op, DB::QuoteEscapeString($value1));
		}

		return $this;
	}

	public function OrderBy($field, $dir = ASC) {
		if(array_key_exists($field, $this->_schema)) {
			$dir = strtoupper($dir) == DESC ? DESC : ASC;
			$this->_orderby[$field] = DB::QuoteIdentifier($field) . ' ' . $dir;
		}
		return $this;
	}

	public function GroupBy($field) {
		if (array_key_exists($field, $this->_schema)) {
			$this->_groupby[] = DB::QuoteIdentifier($field);
		}
		return $this;
	}

	public function Limit($take = false, $skip = false) {
		if ($take !== false && $skip === false) {
			$this->_limit = array($take);
		} else if ($take !== false && $skip !== false) {
			$this->_limit = array($skip, $take);
		} else {
			$this->_limit = array();
		}
		return $this;
	}

	public function Skip($value) { //skip component of Limit (stolen from LINQ)
		// MySQL doesn't like SKIP without TAKE

	}

	public function Take($value) { //take component of Limit (stolen from LINQ)

	}

	public function Distinct($set = null) {
		$this->_distinct = is_bool($set) ? $set : !$this->_distinct;
		return $this;
	}

	public function Count() {
		$query = $this->QueryString(SELECT, 'COUNT(*) AS ' . DB::QuoteIdentifier('Count'));
		$res = DB::Query($query);
		$record = DB::FetchAssoc($res);
		DB::FreeResult($res);
		return $record['Count'];
	}

	//stub functions: logic exists in DBRecordCollection
	public function Each() { //like in jQuery
		$coll = new DBRecordCollection($this);
		return $coll->Each();
	}

	public function Map() { //idem (useful or not???)
		$coll = new DBRecordCollection($this);
		return $coll->Map();
	}

	public function Select($fields = false, $recordCollection = true) {
		$query = $this->QueryString(SELECT, $fields);
		$res = DB::Query($query);

		$resultSet = array();

		$whereCallbacks = count($this->_whereCallback) > 0 ? $this->_whereCallback : false;

		while ($record = DB::FetchAssoc($res)) {
			$record = new $this->_recordClass($record, false);
			DB::Cache($this->_table, $record->PrimaryKey, $record);

			if ($whereCallbacks !== false) {
				$callbackResult = true;
				foreach ($whereCallbacks as $callback) {
					$callbackResult = @call_user_func($callback, $record);
					if (!$callbackResult) {
						break;
					}
				}

				if ($callbackResult === true) {
					$resultSet[] = $record;
				}
			} else {
				$resultSet[] = $record;
			}
		}

		DB::FreeResult($res);

		return $recordCollection ? new DBRecordCollection($resultSet) : $resultSet;
	}

	public function SelectArray($fields = false) {
		$query = $this->QueryString('SELECT', $fields);
		$res = DB::Query($query);

		$resultSet = array();

		if (count($this->_whereCallback) == 0) {
			while ($record = DB::FetchAssoc($res)) {
				$resultSet[] = $record;
			}
		} else {
			while ($record = DB::FetchAssoc($res)) {
				$callbackResult = true;
				foreach ($this->_whereCallback as $callback) {
					$callbackResult = $callbackResult && call_user_func($callback, $record);
				}

				if ($callbackResult === true) {
					$resultSet[] = $record;
				}
			}
		}

		DB::FreeResult($res);

		return $resultSet;
	}

	public function Single($fields = false) {
		$oldLimit = $this->_limit;
		$this->_limit = array(1);

		$query = $this->QueryString(SELECT, $fields);
		$res = DB::Query($query);

		$this->_limit = $oldLimit;

		if (DB::NumRows($res) != 0) {
			$record = new $this->_recordClass(DB::FetchAssoc($res), false);
		} else {
			$record = DB::Null();
		}

		DB::FreeResult($res);

		return $record;
	}

	public function SingleArray($fields = false) {
		$oldLimit = $this->_limit;
		$this->_limit = array(1);

		$query = $this->QueryString(SELECT, $fields);
		$res = DB::Query($query);

		$this->_limit = $oldLimit;

		if (DB::NumRows($res) != 0) {
			$record = DB::FetchAssoc($res);
		} else {
			$record = DB::Null(); // shouldn't this return an empty array??? hrm.
		}

		DB::FreeResult($res);

		return $record;
	}

	public function Scalar($field = false) {
		$row = $this->SingleArray($field);
		return DB::IsNull($row) || DB::IsNull($row[0]) ? DB::Null() : $row[0];
	}

	public function Exists() {
		$oldLimit = $this->_limit;
		$this->_limit = array(1);

		$query = $this->QueryString(SELECT, '1');
		$res = DB::Query($query);

		$this->_limit = $oldLimit;

		$rowcount = DB::NumRows($res);

		DB::FreeResult($res);

		return $rowcount == 1;
	}

	public function Delete() {
		$query = $this->QueryString(DELETE, '');
		DB::Query($query);
		return DB::AffectedRows();
	}

	public function QueryString($operation = SELECT, $fields = false) {
		$operation = strtoupper($operation);
		switch ($operation) {
			case SELECT:
			case UPDATE:
			case DELETE:
			case INSERT:
				break;
			default:
				return false;
		}

		if ($fields === false) {
			$fields = $this->EnumerateSelectFields(true);
		}

		$table = DB::QuoteIdentifier($this->_table);
		$distinct = ($this->_distinct === true && $operation == SELECT) ? ' DISTINCT ' : ' ';
		$where = count($this->_where) > 0 ? ' WHERE ' . implode($this->_where, ' AND ') : '';
		$groupby = count($this->_groupby) > 0 ? ' GROUP BY ' . implode($this->_groupby, ', ') : '';
		$orderby = count($this->_orderby) > 0 ? ' ORDER BY ' . implode($this->_orderby, ', ') : '';
		$limit = count($this->_limit) > 0 ? ' LIMIT ' . implode($this->_limit, ', ') : '';

		$query = $operation . $distinct . $fields . ' FROM ' . $table . $where . $groupby . $orderby . $limit;

		return $query;
	}

	public function NewObject($values = array()) {
		if (is_object($values)) {
			$values = (array)$values;
		}
		if ($this->_pk_unique === true && !isset($values[$this->_pk])) {
			$values[$this->_pk] = DB::UniqueID();
		}
		return new $this->_recordClass($values, true);
	}

	/*
	 * NewRow()
	 * @DEPRECATED
	 */
	public function NewRow($values = array()) {
		return $this->NewObject($values);
	}

	/*
	 * Insert()
	 * @DEPRECATED
	 */
	public function Insert($rows) {
		if ((!is_array($rows) && $rows instanceof $this->_recordClass) || (is_array($rows) && count($rows) > 0 && !is_array($rows[0] ) && !($rows[0] instanceof $this->_recordClass))) {
			$rows = array($rows);
		}

		if (count($rows) >= 1) {
			$valueSets = array();

			foreach ($rows as $rowObj) {
				if ($rowObj instanceof $this->_recordClass) {
					$row = $rowObj->toArray();
				} else if (is_array($rowObj)) {
					$row = $rowObj;
					$rowObj = new $this->_recordClass($rowObj, true);
				}

				if (is_array($row)) {
					$values = array();

					foreach ($this->_schema as $col) {
						$col = $col['name'];

						if ($col != $this->_pk) {
							if (!isset($row[$col]) || $row[$col] === null || $row[$col] instanceof DBNull) {
								$values[] = 'NULL';
							} else if (DB::IsSQLNumericType($col, $this->_schema)) {
								$values[] = $row[$col];
							} else {
								$values[] = DB::QuoteEscapeString($row[$col]);
							}
						}
					}

					$valueSets[] = implode($values, ', ');
				}
			}
		}

		if (count($valueSets) >= 1) {
			$query = sprintf("INSERT INTO `%s` (%s) VALUES (%s);", $this->_table, $this->EnumerateFields(false), implode($valueSets, '), ('));
			$id = DB::Query($query);
		}

		return $id;
	}

	// should only be used for SELECTS
	private function EnumerateFields($includePK = true) {
		$schema = array();
		foreach ($this->_schema as $name => $column) {
			if ($name != $this->_pk || $includePK == true) {
				$schema[] = DB::QuoteIdentifier($name);
			}
		}
		return implode(', ', $schema);
	}

	private function EnumerateSelectFields($includePK = true) {
		if (!empty($this->_selectFields)) {
			return implode(', ', array_keys($this->_selectFields));
		} else {
			if (!$includePK || defined('DB_SELECT_EXPLICIT')) {
				$schema = array();
				foreach ($this->_schema as $name => $column) {
					if ($name != $this->_pk || $includePK == true) {
						$schema[] = DB::QuoteIdentifier($name);
					}
				}
				return implode(', ', $schema);
			} else {
				return '*';
			}
		}
	}

	// Fields, Columns, AddFields, AddColumns:
	// Add support for aliases and duplicate name checking
	public function Fields() {
		$this->_selectFields = array_fill_keys(func_get_args(), true);
		return $this;
	}

	public function Columns() {
		if (!is_array($columns = func_get_arg(0)) || func_num_args() >= 1) {
			$columns = func_get_args();
		}

		$this->_selectFields = array();

		foreach ($columns as $col) {
			if (is_string($col)) {
				$this->_selectFields[DB::QuoteIdentifier($col)] = true;
			}
		}

		return $this;
	}

	public function AddFields() {
		$args = array_fill_keys(func_get_args(), true);
		$this->_selectFields = array_merge($this->_selectFields, $args);
		return $this;
	}

	public function AddColumns() {
		$columns = func_get_args();
		foreach ($columns as $col) {
			$this->_selectFields[DB::QuoteIdentifier($col)] = true;
		}

		return $this;
	}

	public function __get($name) {
		if ($name == 'PrimaryKey') {
			return $this->_pk;
		} else {
			return DB::Null();
		}
	}

	public function __set($name, $value) {

	}

	public function __call($method, $args) {
		if (self::HasPrototype($this, $method)) {
			return self::CallPrototype($this, $method, $args);
		}
	}

	public function __isset($name) {
		return $name == 'PrimaryKey';
	}

	public function getIterator() {
		return new DBRecordCollection($this);
	}
}


/*
 * DBRecordParent
 */
abstract class DBRecordParent extends Prototype implements Iterator, ArrayAccess {
	protected $_table;
	protected $_pk;
	protected $_pkVal;
	protected $_fkSchema;
	protected $_schema;
	protected $_cache;

	protected $_new = false;

	protected $_values = array();
	protected $_updated = array();

	public function __construct($values = false, $new = true) {
		$this->_values = is_array($values) ? $values : array();
		$this->_pkVal = isset($values[$this->_pk]) ? $values[$this->_pk] : null;
		$this->_new = $new;
	}

	public function Delete() {
		if (!$this->_new) {
			DB::Table($this->_table)->Where($this->_pk, EQ, $this->_pkVal)->Delete();
		}
	}

	private function _update() {
		if ($this->_new || !count($this->_updated)) {
			return false;
		}

		$updated = array_keys($this->_updated);
		$sets = array();

		foreach ($updated as $field) {
			$val = $this->_values[$field];

			if (DB::IsNull($val)) {
				$val = 'null';
			} else if (DB::IsSQLNumericType($field, $this->_schema) && is_bool($val)) {
				$val = $val ? 1 : 0;
			} else if (is_string($val)) {
				$val = DB::QuoteEscapeString($val);
			}

			$sets[] = sprintf('%s = %s', DB::QuoteIdentifier($field), $val);
		}

		$query = sprintf('UPDATE %s SET %s WHERE %s = %s LIMIT 1', DB::QuoteIdentifier($this->_table), implode(', ', $sets), DB::QuoteIdentifier($this->_pk), DB::QuoteEscapeString($this->_pkVal));

		DB::Query($query);

		$this->_updated = array();

		return true;
	}

	private function _insert() {
		$fields = array();
		$values = array();

		foreach ($this->_values as $col => $val) {
			// temporary workaround
			if (!isset($this->_schema[$col])) {
				continue;
			}

			if (DB::IsNull($val)) {
				$val = 'null';
			} else if (is_object($val)) {
				if ($val instanceof DBDateTime) {
					$val = DB::QuoteEscapeString($val->Default);
				} else if ($val instanceof DBRecordParent) {
					$val->Save();
					// also handle remote relations?? how?
					$val = $val->PrimaryKey;
				} else if ($val instanceof DBRecordCollection) {
					// handle relation. assigning a DBRecordCollection to a row should make it aware it is a child of this row.
					// allow for creating/fetching a single instance of a DBRecordCollection, and assigning it to multiple parents
					// for this, implement __clone on recordcollection, and have __set call DB::RecordCollection::SetParent($this) or something
					$val->Save();
				}
			} else if (DB::IsSQLNumericType($col, $this->_schema) && is_bool($val)) {
				$val = $val ? 1 : 0;
			} else if (is_string($val)) {
				$val = DB::QuoteEscapeString($val);
			}

			$fields[] = DB::QuoteIdentifier($col);
			$values[] = $val;
		}

		$query = sprintf('INSERT INTO %s (%s) VALUES (%s);', DB::QuoteIdentifier($this->_table), implode($fields, ', '), implode($values, ', '));

		DB::Query($query);

		if (!$this->_pk_unique) {
			$id = DB::InsertID();
			$this->_pkVal = $id;
			$this->_values[$this->_pk] = $id;
		}

		$this->_new = false;
		$this->_updated = array();

		return true;
	}

	public function _exists() {
		//$query = sprintf(
	}

	public function Save() {
		try {
			if (isset($this->_updated[$this->_pk])) {
				$this->_new = $this->_exists();
			}
			return $this->_new ? $this->_insert() : $this->_update();
		} catch (Exception $ex) {
			return false;
		}
	}

	public function SetValue($name, $value) {
		$this->__set($name, $value);
		return $this;
	}

	public function SetValues($nvpairs) {
		foreach ($nvpairs as $name => $value) {
			$this->__set($name, $value);
		}
		return $this;
	}

	public function GetValue($name) {
		return $this->__get($name);
	}

	public function __call($method, $args) {
		if (self::HasPrototype($this, $method)) {
			return self::CallPrototype($this, $method, $args);
		}

		if (isset($this->_fkSchema[$method])) {
			extract($this->_fkSchema[$method]);
			return DB::Table($reftbl)->Where($refcol, EQ, $this->_values[$col]);
		} else if (isset($this->_values[$method])) {
			$datatype = $this->_schema[$method]['type'];

			if ($datatype == 'datetime' || $datatype == 'timestamp') {
				$dtObj = new DBDateTime($this->_values[$method]);

				if (count($args) == 0) {
					return $dtObj;
				} else {
					return $dtObj->Format($args[0]);
				}
			} else {
				return $this->_values[$method];
			}
		} else {
			return DB::Null();
		}
	}

	public function __get($name) {
		if (isset($this->_fkSchema[$name])) {

			if (isset($this->_cache[$name])) {
				return $this->_cache[$name];
			} else {
				extract($this->_fkSchema[$name]);

				if (DB::IsNull($this->_values[$col])) {
					return DB::Null();
				}

				$tbl = DB::Table($reftbl);

				if ($tbl->PrimaryKey == $refcol) {
					$tmp = $tbl->ID($this->_values[$col]);
				} else {
					$tbl->Where($refcol, EQ, $this->_values[$col]);
					if ($sel == 'Single') {
						$tmp = $tbl->Single();
					} else {
						$tmp = new DBRecordCollection($tbl, $this);
					}
				}

				$this->_cache[$name] = $tmp;

				return $tmp;
			}
		} else if (isset($this->_values[$name])) {
			if (isset($this->_schema[$name])) {
				$datatype = $this->_schema[$name]['type'];

				if (($datatype == 'datetime' || $datatype == 'timestamp') && !($this->_values[$name] instanceof DBDateTime)) {
					$this->_values[$name] = new DBDateTime($this->_values[$name]);
				}
			}

			return $this->_values[$name];
		} else if ($name == 'PrimaryKey') {
			return $this->_pkVal;
		} else {
			return DB::Null();
		}
	}

	public function __set($name, $value) {
		// should be able to set primary key.  need an _exists method to check before writing though
		// in Save method, if (isset($this->_updated[$this->_pk])) $this->_new = $this->_exists();
		//if (isset($this->_schema[$name]) && $name != $this->_pk) {
		if (isset($this->_schema[$name])) {
			$datatype = $this->_schema[$name]['type'];

			if ($datatype == 'datetime' || $datatype == 'timestamp' && !($value instanceof DBDateTime)) {
				$value = new DBDateTime($value);
			}

			$this->_updated[$name] = true;
		}

		$this->_values[$name] = $value;

	}

	public function __isset($name) {
		return isset($this->_fkSchema[$name]) || isset($this->_values[$name]);
	}

	public function __clone() {
		$values = clone $this->_values;
		unset($values[$this->_pk]);
		$className = get_class($this);

		return new $className($values);
	}

	public function __toString() {
		return DB::IfNull(isset($this->_default) ? $this->_values[$this->_default] : null, $this->_values['Name'], $this->_pkVal);
	}

	public function __sleep() {
		return array(
			"\0*\0"."_pkVal",
			"\0*\0"."_cache",
			"\0*\0"."_new",
			"\0*\0"."_values",
			"\0*\0"."_updated"
		);
	}

	public function toArray() {
		return clone $this->_values;
	}

	public function rewind() {
		reset($this->_values);
	}

	public function current() {
		return current($this->_values);
	}

	public function key() {
		return key($this->_values);
	}

	public function next() {
		return next($this->_values);
	}

	public function valid() {
		return $this->current() !== false;
	}

	public function offsetSet($offset, $value) {
		if ($offset != $this->_pk) {
			$this->_values[$offset] = $value;
		}
	}

	public function offsetExists($offset) {
		return isset($this->_values[$offset]);
	}

	public function offsetUnset($offset) {
		if ($offset != $this->_pk) {
			unset($this->_values[$offset]);
		}
	}

	public function offsetGet($offset) {
		return isset($this->_values[$offset]) ? $this->_values[$offset] : DB::Null();
	}
}

/*
 * DBRecordJoin
 */
class DBRecordJoin extends Prototype {

}


/*
 * DBDateTime
 */
class DBDateTime extends Prototype {
	private $_value;

	public function __construct($value) {
		if (!is_numeric($value)) {
			$this->_value = strtotime($value);
		} else {
			$this->_value = $value;
		}
	}

	public static function Parse($time) {
		return new DBDateTime($time);
	}

	public static function Now() {
		return new DBDateTime(time());
	}

	public function Format($format) {
		if (is_string($format)) {
			if (substr($format, 0, 2) == '@@') {
				$format = substr($format, 2);
				if (strpos($format, '::')) {
					$format = explode('::', $format);
				}
				if (is_callable($format)) {
					return call_user_func($format, $this);
				}
			} else {
				return date($format, $this->_value);
			}
		} else if (is_array($format) && is_callable($format)) {
			return call_user_func($format, $this);
		} else if (is_object($format) && $format instanceof Closure) {
			return $format($this->_value);
		}
	}

	public function __get($name) {
		$name = strtoupper($name);

		if ($name == 'VALUE' || $name == 'UNIXTIME') {
			return $this->_value;
		} else if ($name == 'DEFAULT') {
			return $this->__toString();
		} else if (defined('DATE_' . $name)) {
			return $this->Format(constant('DATE_' . $name));
		} else {
			return DB::Null();
		}
	}

	public function __toString() {
		return $this->Format('Y-m-d H:i:s');
	}

}


/*
 * DBNull
 */
final class DBNull {
	public function &__get($name) {
		return $this;
	}

	public function __set($name, $value) {
		return false;
	}

	public function &__call($method, $args) {
		return $this;
	}

	public function __toString() {
		return 'NULL';
	}
}


/*
 * DBRecordCollection
 */
final class DBRecordCollection extends Prototype implements Iterator, ArrayAccess, Countable {
	private $_index = false;

	protected $_query = false;
	private $_parent = false;
	private $_binding = false;

	private $_map = false;
	protected $_records = false;

	public function __construct($records, $parent = null) {
		if ($records instanceof DBTableParent) {
			$this->_query = $records;
		} else if ($records instanceof DBRecordCollection) {
			$this->_query = $records->_query;
			$this->_records = $records->_records;
		} else if (is_array($records)){
			$this->_records = $records;
			reset($this->_records);
		}

		if ($parent !== null && $parent instanceof DBRecordParent) {
			$this->_parent = $parent;
		}
	}

	private function _loadQuery() {
		if ($this->_query !== false && $this->_records === false) {
			$this->_records = $this->_query->Select(false, false);
			reset($this->_records);
		}
	}

	public function Item($index) {
		$this->_loadQuery();
		return $this->_records[$index];
	}

	public function Refresh() {
		if ($this->_query !== false) {
			$this->_records = false;
			$this->_loadQuery();
		}
	}

	public function Add($record) {
		return $this->_records[] = $record;
	}

	public function Index($field = false) {
		$this->_loadQuery();
		if ($field === false) {
			if ($this->_index === false) {
				$field = 'PrimaryKey';
			} else {
				$field = $this->_index;
			}
		}

		$this->_index = $field;
		$this->_map = array();

		foreach ($this->_records as $key => $record) {
			$this->_map[$record->$field] = $key;
		}

		reset($this->_records);
		return $this;
	}

	//public function Each() //like in jQuery
	//public function Map() //idem (useful or not???)

	public function Sort($field = false, $order = false) {
		$this->_loadQuery();
		natcasesort($this->_records);
		if ($this->_index !== false) $this->Index();
		return $this;
	}

	public function Save() {
		// saves all records: does updates where necessary, and does inserts where necessary.
		// potentially group inserts together like: INSERT `Tbl` (`Fields`) VALUES (obj1), (obj2);
	}

	public function count() {
		if ($this->_records !== false) {
			return count($this->_records);
		} else if ($this->_query !== false) {
			return $this->_query->Count();
		}
	}

	public function rewind() {
		$this->_loadQuery();
		reset($this->_records);
	}

	public function current() {
		$this->_loadQuery();
		return current($this->_records);
	}

	public function key() {
		$this->_loadQuery();
		if ($this->_index != false) {
			$cur = current($this->_records);
			$field = $this->_index;
			return $cur->$field;
		} else {
			return key($this->_records);
		}
	}

	public function next() {
		$this->_loadQuery();
		return next($this->_records);
	}

	public function valid() {
		$this->_loadQuery();
		return $this->current() !== false;
	}

	// $offset is null if called as $coll[] = $value;
	public function offsetSet($offset, $value) {
		//$this->records[$offset] = $value;
	}

	public function offsetExists($offset) {
		$this->_loadQuery();
		if ($this->_index !== false) {
			return isset($this->_map[$offset]);
		} else {
			return isset($this->_records[$offset]);
		}
	}

	public function offsetUnset($offset) {
		$this->_loadQuery();
		if ($this->_index !== false) {
			unset($this->_records[$this->_map[$offest]]);
			unset($this->_map[$offest]);
		} else {
			unset($this->_records[$offset]);
		}
	}

	public function offsetGet($offset) {
		$this->_loadQuery();
		if ($this->_index !== false) {
			return isset($this->_map[$offset]) ? $this->_records[$this->_map[$offset]] : DB::Null();
		} else {
			return isset($this->_records[$offset]) ? $this->_records[$offset] : DB::Null();
		}
	}
}


/*
 * DBException
 */
class DBException extends Exception {
	private $_query;

	public function __construct($message, $code, $query = '') {
		parent::__construct($message, $code);
		$this->_query = $query;
	}

	public function GetQuery() {
		return $this->_query;
	}
}


/*
 * DBGenericTable
 */
class DBGenericTable extends DBTableParent {
	protected $_db = null;
	protected $_table = null;
	protected $_recordClass = 'DBGenericRecord';
	protected $_schema = array();
	protected $_pk = null;
	protected $_keys = array();
}


/*
 * DBGenericRecord
 */
class DBGenericRecord extends DBRecordParent {
	protected $_db = null;
	protected $_schema = array();
	protected $_table = null;
	protected $_pk = null;
	protected $_fkSchema = array();
	protected $_default = null;

	public function __construct($values = false, $new = false) {
		$this->_values = is_array($values) ? $values : array();
		$this->_pkVal = null;
		$this->_new = false;
	}

	public function Save() {
		//override save
		return false;
	}
}


/*
 * DBTransaction
 */
class DBTransaction {
	private static $_instance = false;

	private $_active = false;
	private $_isolation = false;

	public static function GetInstance() {
		if (self::$_instance == false) {
			self::$_instance = new DBTransaction();
		}
		return self::$_instance;
	}

	private function __construct() {
	}

	public function Start() {
		if ($this->_active) return false;

		DB::Query("START TRANSACTION;", false);
		$this->_active = true;

		return true;
	}

	public function Commit() {
		if (!$this->_active) {
			return false;
		}

		DB::Query("COMMIT;", false);
		$this->_active = false;

		return true;
	}

	public function Rollback() {
		if (!$this->_active) {
			return false;
		}

		DB::Query('ROLLBACK;', false);
		$this->_active = false;

		return true;
	}

	public function IsActive() {
		return $this->_active;
	}

	public function __get($name) {
		if (strtoupper($name) == 'ISOLATIONLEVEL') {
			return $this->_isolation;
		}
	}

	public function __set($name, $value) {
		if (strtoupper($name) == 'ISOLATIONLEVEL') {
			$this->_isolation = $value;
		}
	}

}

/*
class ... implements ArrayAccess, Serializable {
	public function offsetSet($offset, $value) {
		//$this->container[$offset] = $value;
	}

	public function offsetExists($offset) {
		//return isset($this->container[$offset]);
	}

	public function offsetUnset($offset) {
		//unset($this->container[$offset]);
	}

	public function offsetGet($offset) {
		//return isset($this->container[$offset]) ? $this->container[$offset] : null;
	}

	public function serialize() {
		//return serialize($this->data);
	}

	public function unserialize($data) {
		//$this->data = unserialize($data);
	}
}
*/
